﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Kokomo.Lexical.Coding;
using Kokomo.Lexical.Expressions;
using System.Diagnostics;
using System.Reflection;
using System.Reflection.Emit;
using CodeLabel = Kokomo.Lexical.Coding.Label;
using Kokomo.PeInspector.ObjectGraphs;
using Kokomo.Mirror.CliMetadata;

namespace Kokomo.PeInspector.Reflection.Disassembly.Flow
{
	abstract class FlowAnalyzer
	{
		protected MsilDecompilationContext Context { get; private set; }
		public FlowAnalyzer ParentAnalyzer { get; private set; }

		public int BlockStartOffset { get; private set; }

		private int _blockEndOffset;
		public int BlockEndOffset
		{
			get { return this._blockEndOffset; }
			//set
			//{
			//    if (this.ParentAnalyzer != null)
			//    {
			//        // UNDONE: Prevents switch expressions in loop control blocks
			//        //if (value > this.ParentAnalyzer.BodyEndOffset)
			//        if (value > this.ParentAnalyzer.BlockEndOffset)
			//            throw new ArgumentException("Block end offset must fit within body of parent");
			//    }
			//    this._blockEndOffset = value;
			//}
		}

		public virtual bool CanExtendBlockTo(int newEndOffset)
		{
			return
				(this.ParentAnalyzer == null)
				|| (this.ParentAnalyzer.BlockEndOffset >= newEndOffset)
				|| (this.ParentAnalyzer.CanExtendBlockTo(newEndOffset))
				;
		}
		public void ExtendBlockTo(int newEndOffset)
		{
			if (!this.CanExtendBlockTo(newEndOffset))
				throw new InvalidOperationException();

			if (
				(this.ParentAnalyzer != null)
				&& (this.ParentAnalyzer.BlockEndOffset < newEndOffset)
				)
			{
				this.ParentAnalyzer.ExtendBlockTo(newEndOffset);
			}

			this._blockEndOffset = newEndOffset;
		}

		public int BodyStartOffset { get; protected set; }

		private int _bodyEndOffset;
		public int BodyEndOffset
		{
			get { return this._bodyEndOffset; }
			set
			{
				if (value > this.BlockEndOffset)
					throw new ArgumentException(Messages.BodyEndOffsetOutOfRangeMessage);
				this._bodyEndOffset = value;
			}
		}

		public List<LocalVariable> ExternalLocalReferences { get; private set; }

		public void DeclareExternal(VariableDeclarationStatement declaration)
		{
			if (declaration == null) throw new ArgumentNullException("declaration");

			if (this.ActiveStatementBlock != null)
			{
				this.ActiveStatementBlock.Add(declaration);
			}
			else if (this.ParentAnalyzer != null)
			{
				this.ParentAnalyzer.DeclareExternal(declaration);
			}
			else
			{
				throw new InvalidOperationException("No active statement block");
			}
		}

		protected FlowAnalyzer(
			MsilDecompilationContext context,
			int blockStartOffset,
			int blockEndOffset,
			int bodyStartOffset,
			int bodyEndOffset
			)
		{
			if (context == null) throw new ArgumentNullException("context");

			if (blockStartOffset < 0) throw new ArgumentOutOfRangeException("blockStartOffset", blockStartOffset, "Value must be >= 0");
			if (blockEndOffset < blockStartOffset) throw new ArgumentOutOfRangeException("blockEndOffset", blockEndOffset, "Value must be >= blockStartOffset");
			if (bodyStartOffset < 0) throw new ArgumentOutOfRangeException("bodyStartOffset", bodyStartOffset, "Value must be >= 0");
			if (bodyEndOffset < 0) throw new ArgumentOutOfRangeException("bodyEndOffset", bodyEndOffset, "Value must be >= bodyStartOffset");

			this.Context = context;

			this.BlockStartOffset = blockStartOffset;
			this._blockEndOffset = blockEndOffset;
			this.BodyStartOffset = bodyStartOffset;
			this.BodyEndOffset = bodyEndOffset;

			this.ExternalLocalReferences = new List<LocalVariable>();
		}

		protected FlowAnalyzer(
			FlowAnalyzer parentAnalyzer,
			int blockStartOffset,
			int blockEndOffset,
			int bodyStartOffset,
			int bodyEndOffset
			)
			: this(parentAnalyzer.Context, blockStartOffset, blockEndOffset, bodyStartOffset, bodyEndOffset)
		{
			if (parentAnalyzer == null) throw new ArgumentNullException("parentAnalyzer");
			this.ParentAnalyzer = parentAnalyzer;
		}

		public override string ToString()
		{
			return string.Format("Block=[{0:X4}, {1:X4}], Body=[{2:X4}, {3:X4}]",
				this.BlockStartOffset,
				this.BlockEndOffset,
				this.BodyStartOffset,
				this.BodyEndOffset
				);
		}

		public bool BlockContains(int offset)
		{
			bool isWithinBlock =
				(offset >= this.BlockStartOffset)
				&& (offset < this.BlockEndOffset)
				;
			return isWithinBlock;
		}
		public bool BodyContains(int offset)
		{
			bool isWithinBody =
				(offset >= this.BodyStartOffset)
				&& (offset < this.BodyEndOffset)
				;
			return isWithinBody;
		}

		private IList<Statement> _activeStatementBlock;
		public IList<Statement> ActiveStatementBlock
		{
			get { return this._activeStatementBlock; }
			protected set
			{
				if (value != this._activeStatementBlock)
				{
					this._activeStatementBlock = value;
					this.OnActiveStatementBlockChanged();
				}
			}
		}
		public event EventHandler ActiveStatementBlockChanged;
		protected virtual void OnActiveStatementBlockChanged()
		{
			if (this.ActiveStatementBlockChanged != null) this.ActiveStatementBlockChanged(this, EventArgs.Empty);
		}

		protected LoopFlowAnalyzer TryGetDoLoopContext(int? endOffsetLowerLimit)
		{
			MsilDecompilationContext context = this.Context;

			bool isBlockStart = context.IsBlockStart();
			if (isBlockStart)
			{
				MsilTransition previousTransition = context.GetTransitionJustBefore(context.CurrentOffset);
				bool hasFallthrough =
					(previousTransition == null)
					|| (this.BodyStartOffset == context.CurrentOffset)
					|| (previousTransition.IsConditional)
					;

				if (hasFallthrough)
				{
					var branches = context.FindBranchesTo(context.CurrentOffset);

					MsilBranch entryBranch = null;
					foreach (var branch in branches)
					{
						bool isEntryBranch =
							(branch.IsConditional)
							&& this.BodyContains(branch.SourceOffset)
							&& (!endOffsetLowerLimit.HasValue || (branch.SourceOffset > endOffsetLowerLimit.Value))
							&& (branch.SourceOffset > this.Context.CurrentOffset)
							&& ((entryBranch == null) || (branch.SourceOffset >= entryBranch.SourceOffset))
							;
						if (isEntryBranch) entryBranch = branch;
					}

					if (entryBranch != null)
					{
						LoopStatement loopStatement = new LoopStatement(null);
						int blockStart = context.CurrentOffset;
						int exitOffset = context.GetBlockEnd(entryBranch.SourceOffset);
						int guardBlockOffset = context.GetBlockStartBefore(entryBranch.SourceOffset);
						//if (guardBlockOffset == blockStart)
						{
							guardBlockOffset = entryBranch.SourceOffset;
							// Offset is the branch, so back up until just before the bracnh
							guardBlockOffset = context.GetInstructionJustBefore(guardBlockOffset).Offset;
						}
						LoopFlowAnalyzer doLoopInfo = new LoopFlowAnalyzer(
							this.Context.CurrentFlow,
							loopStatement,
							blockStart,
							exitOffset,
							blockStart,
							guardBlockOffset
							);
						return doLoopInfo;
					}
				}
			}

			return null;
		}

		protected LoopFlowAnalyzer TryGetWhileLoopAnalyzer(MsilBranch branch)
		{
			MsilDecompilationContext context = this.Context;
			Instruction instruction = context.CurrentInstruction;
			MsilBranch guardBranch = context.GetTransitionAt(instruction.Offset) as MsilBranch;

			if (
				(guardBranch != null)
				&& (guardBranch.TargetOffset > instruction.Offset)
				&& (!guardBranch.IsConditional)
				)
			{

				MsilBranch loopBranch = null;
				var possibleLoopBranches = context.FindBranchesTo(instruction.NextOffset);
				//context.GetTransitionAfter(guardBranch.TargetOffset) as MsilBranch;
				foreach (var possibleLoopBranch in possibleLoopBranches)
				{
					if (
						(possibleLoopBranch.SourceOffset >= guardBranch.TargetOffset)
						&& (
							(loopBranch == null)
							|| (possibleLoopBranch.SourceOffset < loopBranch.SourceOffset)
							)
						)
					{
						loopBranch = possibleLoopBranch;
					}
				}

				if (
					(loopBranch != null)
					&& (loopBranch.TargetOffset == instruction.NextOffset)
					)
				{
					int blockStartOffset = context.CurrentOffset;
					int bodyStartOffset = context.CurrentInstruction.NextOffset;
					int exitOffset = context.GetBlockEnd(loopBranch.SourceOffset);

					LoopStatement loopStatement = new LoopStatement(null) { EvaluateInitially = true };
					LoopFlowAnalyzer flow = new LoopFlowAnalyzer(
						this.Context.CurrentFlow,
						loopStatement,
						blockStartOffset,
						exitOffset,
						bodyStartOffset,
						guardBranch.TargetOffset
						);

					return flow;
				}
			}

			return null;
		}

		protected ExceptionHandlingClause GetLongestTryBlock(int tryOffset)
		{
			ExceptionHandlingClause longestTryBlock = null;
			foreach (var clause in this.Context.MethodBody.ExceptionHandlingClauses)
			{
				bool itFits =
					(clause.TryOffset == tryOffset)
					&& this.BodyContains(clause.TryOffset + clause.TryLength)
					;
				bool itWins =
					(longestTryBlock == null)
					|| (clause.TryLength > longestTryBlock.TryLength)
					;
				if (itFits && itWins)
				{
					longestTryBlock = clause;
				}
			}

			return longestTryBlock;
		}

		protected virtual void AnalyzeLocalBranch(MsilBranch branch)
		{
			this.AnalyzeInnerBranch(branch, this);
		}
		protected virtual void AnalyzeInnerBranch(MsilBranch branch, FlowAnalyzer innerFlow)
		{
			MsilDecompilationContext context = this.Context;

			if (this.ParentAnalyzer != null)
			{
				this.ParentAnalyzer.AnalyzeInnerBranch(branch, innerFlow);
			}
			else
			{
				LoopFlowAnalyzer whileLoopInfo = this.TryGetWhileLoopAnalyzer(branch);
				if (whileLoopInfo != null)
				{
					context.Advance();
					innerFlow.AnalyzeInnerBlock(whileLoopInfo);
					innerFlow.ActiveStatementBlock.Add(whileLoopInfo.LoopStatement);
				}
				else
				{
					if (branch.TargetOffset != context.CurrentInstruction.NextOffset)
					{
						CodeLabel targetLabel = this.Context.GetLabel(branch.TargetOffset);
						context.RecordStatement(new BranchStatement(targetLabel));
					}
					context.Advance();
				}
			}

		}

		protected virtual void AnalyzeSwitch(MsilSwitch @switch)
		{
			MsilBranchTarget[] switchBranchTargets = @switch.TargetOffsets;

			MsilStackEntry conditionStackEntry = this.Context.PopStackEntry();

			SwitchFlowAnalyzer switchFlow = new SwitchFlowAnalyzer(
				this.Context.CurrentFlow,
				this.Context.CurrentOffset,
				this.Context.CurrentInstruction.NextOffset,
				switchBranchTargets,
				conditionStackEntry
				);

			MsilDecompilationContext context = this.Context;

			context.Advance();
			this.Context.CurrentFlow.AnalyzeInnerBlock(switchFlow);

			context.Statements.Add(switchFlow.SwitchStatement);
		}

		protected virtual void AnalyzeLocalConditionalBranch(MsilBranch branch)
		{
			this.AnalyzeInnerConditionalBranch(this, branch);
		}

		protected virtual void AnalyzeInnerConditionalBranch(FlowAnalyzer innerFlow, MsilBranch branch)
		{
			if (this.ParentAnalyzer != null)
			{
				this.ParentAnalyzer.AnalyzeInnerConditionalBranch(innerFlow, branch);
			}
			else
			{
				Expression condition = this.Context.PopExpression();

				if (
					(branch.TargetOffset > this.Context.CurrentOffset)
					&& (
						innerFlow.BlockContains(branch.TargetOffset)
						|| (branch.TargetOffset == innerFlow.BodyEndOffset)
						)
					// UNDONE: Prevents switch expressions in loop control blocks
					//&& innerFlow.BodyContains(branch.TargetOffset)
					)
				{
					int bodyStartOffset = this.Context.CurrentInstruction.NextOffset;
					int endOffset = branch.TargetOffset;

					this.Context.Advance();
					condition = condition.CoerceTo(typeof(bool));
					condition = !condition;
					condition = condition.CoerceTo(typeof(bool));
					ConditionFlowAnalyzer conditionFlow = new ConditionFlowAnalyzer(
						innerFlow,
						condition,
						branch.SourceOffset,
						endOffset,
						bodyStartOffset,
						endOffset
						);

					innerFlow.AnalyzeInnerBlock(conditionFlow);
					if (conditionFlow.SwitchStatement != null)
					{
						this.Context.Statements.Add(conditionFlow.SwitchStatement);
					}
					else if (conditionFlow.ConditionStatement != null)
					{
						// Check for anonymous delegate
						BackreferenceTemplate<FieldReferenceExpression> delegateCacheField = new BackreferenceTemplate<FieldReferenceExpression>(
							new MemberTemplate[] {
								new PropertyTemplate<FieldInfo>(
									"Field",
									new DelegatedTemplate<FieldInfo>( ReflectionExtensions.IsAnonymousDelegateCacheField)
									)
							},
							FieldReferenceEqualityComparer.Instance
							);
						ObjectTemplate<MethodReferenceExpression> methodReferenceTemplate = new ObjectTemplate<MethodReferenceExpression>();
						ObjectTemplate<ConditionStatement> anonymousDelegateInitializerTemplate = new ObjectTemplate<ConditionStatement>(
							new MemberTemplate[] {
								new PropertyTemplate<BinaryOperationExpression>(
									"Condition",
									new ObjectTemplate<BinaryOperationExpression>(
										new MemberTemplate[] {
											new PropertyTemplate<FieldReferenceExpression>(
												"Left",
												delegateCacheField
												),
											new PropertyTemplate<ConstantExpression>(
												"Right",
												new NodeEqualityTemplate<ConstantExpression>(NullConstantExpression.Instance)
												)
										})
									),
								new PropertyTemplate<IEnumerable<Statement>>(
									"TrueStatements",
									new EnumerableTemplate<Statement>(
										new ElementTemplate[] {
											new ElementTemplate<AssignmentStatement>(
												0,
												new ObjectTemplate<AssignmentStatement>(
													new MemberTemplate[] {
														new PropertyTemplate<FieldReferenceExpression>(
															"Variable",
															delegateCacheField
															),
														new PropertyTemplate<NewObjectExpression>(
															"Initializer",
															new ObjectTemplate<NewObjectExpression>(
																new MemberTemplate[] {
																	new PropertyTemplate<IEnumerable<Expression>>(
																		"Arguments",
																		new EnumerableTemplate<Expression>(
																			new ElementTemplate[] {
																				new ElementTemplate<MethodReferenceExpression>(
																					0,
																					methodReferenceTemplate
																					)
																			})
																		)
																})
															)
													})
												)
										})
									)
							});

						bool isAnonymousDelegate = anonymousDelegateInitializerTemplate.Inspect(conditionFlow.ConditionStatement);
						if (isAnonymousDelegate)
						{
							// Build anonymous method delegate expression
							MethodInfo anonymousMethod = methodReferenceTemplate.CapturedValue.Method;
							ParameterInfo[] parameters = anonymousMethod.GetParameters();
							TypedVariable[] delegateParameters = new TypedVariable[parameters.Length];
							for (int i = 0; i < parameters.Length; i++)
							{
								ParameterInfo parameter = parameters[i];
								TypedVariable parameterVariable = new WorkspaceVariable(parameter.Name, parameter.ParameterType);
								delegateParameters[i] = parameterVariable;
							}

							IEnumerable<Statement> delegateStatements = null;
							try
							{
								MethodDecompilation anonymousDisassembly = MsilDecompiler.Decompile(anonymousMethod);
								delegateStatements = anonymousDisassembly.Statements;
							}
							catch
							{
								// ignore disassembly errors
								delegateStatements = new Statement[0];
							}

							Expression delegateExpression = null;
							bool isLambda = delegateStatements.Count() == 1 && (delegateStatements.ElementAt(0) is ReturnStatement);
							if (isLambda)
							{
								ReturnStatement returnStatement = (ReturnStatement)delegateStatements.ElementAt(0);
								LambdaExpression lambda = new LambdaExpression(returnStatement.Expression, delegateParameters);
								delegateExpression = lambda;
							}
							else
							{
								delegateExpression = new AnonymousMethodExpression(delegateParameters, delegateStatements);
							}

							this.Context.AnonymousMethodDelegateTable[delegateCacheField.CapturedValue.Field] = delegateExpression;
						}
						else
						{
							this.Context.Statements.Add(conditionFlow.ConditionStatement);
						}
					}
				}
				else
				{
					var currentStatementBlock = innerFlow.ActiveStatementBlock;
					ConditionStatement branchConditionStatement = new ConditionStatement(condition);
					innerFlow.ActiveStatementBlock = branchConditionStatement.TrueStatements;
					innerFlow.AnalyzeLocalBranch(branch);
					innerFlow.ActiveStatementBlock = currentStatementBlock;
					currentStatementBlock.Add(branchConditionStatement);
				}
			}
		}

		protected virtual void OnEnterBlock()
		{
		}
		protected virtual void OnEnterBody()
		{
		}
		protected virtual void OnExitBody()
		{
			// Force the context to flush any ghosted variables
			this.ActiveStatementBlock = null;
		}
		protected virtual void OnExitBlock()
		{
		}

		public FlowAnalyzer ActiveChild { get; private set; }
		//public MsilFlowAnalyzer CurrentFlow
		//{
		//    get
		//    {
		//        return (this.InnerBlock != null) ? this.InnerBlock.CurrentFlow : this;
		//    }
		//}

		protected void AnalyzeInnerBlock(FlowAnalyzer inner)
		{
			if (inner == null) throw new ArgumentNullException("inner");

			if (this.ActiveChild != null)
			{
				throw new InvalidOperationException("This flow already has an inner block");
			}
			else
			{
				//if (this.InnerBlock != null) throw new InvalidOperationException("Already processing an inner flow");

				MsilDecompilationContext context = this.Context;

				this.ActiveChild = inner;
				var priorFlow = context.CurrentFlow;
				inner.Analyze();
				context.CurrentFlow = priorFlow;
				this.ActiveChild = null;

				// Promote references
				foreach (var variable in inner.ExternalLocalReferences)
				{
					if (
						!this.ExternalLocalReferences.Contains(variable)
						&& (variable.Usage != LocalVariableUsage.Decision)
						)
					{
						VariableDeclarationStatement declaration = new VariableDeclarationStatement(variable);
						this.DeclareExternal(declaration);
					}
				}
			}
		}

		protected ExceptionFlowAnalyzer GetExceptionFlow(int tryLength)
		{
			IEnumerable<ExceptionHandlingClause> clauses = this.Context.GetExceptionHandlingClauses(this.Context.CurrentOffset, tryLength);
			clauses = clauses.OrderBy(c => c.HandlerOffset);
			ExceptionHandlingClause lastClause = clauses.Last();
			int endOffset = lastClause.HandlerOffset + lastClause.HandlerLength;

			ExceptionFlowAnalyzer exceptionFlow = new ExceptionFlowAnalyzer(this.Context.CurrentFlow, this.Context.CurrentOffset, endOffset, tryLength, clauses);

			return exceptionFlow;
		}

		public virtual void Analyze()
		{
			MsilDecompilationContext context = this.Context;

			context.CurrentFlow = this;

			foreach (var local in context.LocalVariables)
			{
				if (context.DeclaredVariables.Contains(local)) continue;

				bool hasBlockReference = context.IsVariableReferencedWithin(local, this.BlockStartOffset, this.BlockEndOffset);
				bool hasBodyReference = context.IsVariableReferencedWithin(local, this.BodyStartOffset, this.BodyEndOffset);
				bool hasExternalReferences = context.IsVariableReferencedOutsideOf(local, this.BodyStartOffset, this.BodyEndOffset);

				bool hasControlReferences = hasBlockReference && !hasBodyReference;

				bool mustDeclareExternally =
					(hasExternalReferences && hasBlockReference)
					|| (hasBodyReference && hasControlReferences)
					;
				bool implicitDeclaration =
					!hasExternalReferences
					&& !hasBodyReference
					;

				if (implicitDeclaration || mustDeclareExternally)
				{
					context.DeclaredVariables.Add(local);
				}
				if (mustDeclareExternally)
				{
					this.ExternalLocalReferences.Add(local);
				}
			}

			this.OnEnterBlock();
			bool inBody = false;

			while (
				(context.CurrentInstruction != null)
				&& this.BlockContains
				(context.CurrentOffset)
				)
			{
				// UNDONE: Not required after adding context.CurrentFlow property
				//context.Statements = this.ActiveStatementBlock;

				if (!inBody && this.BodyContains(context.CurrentOffset))
				{
					inBody = true;
					this.OnEnterBody();
				}

				// Check for exception clauses
				int? longestTryLength = context.GetLongestTryBlockAt(context.CurrentOffset, this.BodyEndOffset);

				LoopFlowAnalyzer doLoopAnalyzer = this.TryGetDoLoopContext(longestTryLength);
				if (doLoopAnalyzer != null)
				{
					this.AnalyzeInnerBlock(doLoopAnalyzer);
					this.ActiveStatementBlock.Add(doLoopAnalyzer.LoopStatement);
				}
				else if (longestTryLength.HasValue)
				{
					ExceptionFlowAnalyzer exceptionFlow = this.GetExceptionFlow(longestTryLength.Value);
					context.FlushGhosts();
					Statement previousStatement = this.ActiveStatementBlock.LastOrDefault();
					this.AnalyzeInnerBlock(exceptionFlow);

					// Check for foreach loop
					ObjectTemplate<LocalVariable> enumeratorVariableTemplate = new ObjectTemplate<LocalVariable>();
					ObjectTemplate<Expression> enumerableExpressionTemplate = new ObjectTemplate<Expression>();
					ObjectTemplate<VariableDeclarationStatement> enumeratorDeclarationTemplate = new ObjectTemplate<VariableDeclarationStatement>(
						new MemberTemplate[] {
							new PropertyTemplate<LocalVariable>("Variable", enumeratorVariableTemplate),
							new PropertyTemplate<BoundFunctionCallExpression>(
								"Initializer",
								new ObjectTemplate<BoundFunctionCallExpression>(
									new MemberTemplate[] {
										new PropertyTemplate<BoundMethodCallFunction>(
											"Function",
											new ObjectTemplate<BoundMethodCallFunction>(
												new MemberTemplate[] {
													new PropertyTemplate<MethodInfo>(
														"Method",
														new ObjectTemplate<MethodInfo>(
															new MemberTemplate[]{
																new PropertyTemplate<string>(
																	"Name",
																	new NodeEqualityTemplate<string>("GetEnumerator")
																)}
															)
														)
												})
											),
										new PropertyTemplate<Expression[]>(
											"Arguments",
											new ArrayTemplate<Expression>(
												new ElementTemplate[] { new ElementTemplate<Expression>(0, enumerableExpressionTemplate) },
												new MemberTemplate[] {
													new PropertyTemplate<int>("Length", new NodeEqualityTemplate<int>(1))
												}
												)
											)
									})
								)
						});

					bool isForEachLoop = false;
					if (enumeratorDeclarationTemplate.Inspect(previousStatement))
					{
						ObjectTemplate<LocalVariable> itemVariableTemplate = new ObjectTemplate<LocalVariable>();
						ObjectTemplate<VariableDeclarationStatement> itemVariableDeclarationTemplate = new ObjectTemplate<VariableDeclarationStatement>(
							new MemberTemplate[] {
								new PropertyTemplate<LocalVariable>(
									"Variable",
									itemVariableTemplate
									)
							});
						EnumerableTemplate<Statement> loopStatementsTemplate = new EnumerableTemplate<Statement>(
							new ElementTemplate[] {
								new ElementTemplate<VariableDeclarationStatement>(
									0,
									itemVariableDeclarationTemplate
									)
							});
						ObjectTemplate<TryBlock> tryBlockTemplate = new ObjectTemplate<TryBlock>(
							new MemberTemplate[] {
								new PropertyTemplate<IEnumerable<Statement>>(
									"TryStatements",
									new EnumerableTemplate<Statement>(
										new ElementTemplate[] {
											new ElementTemplate<LoopStatement>(
												0,
												new ObjectTemplate<LoopStatement>(
													new MemberTemplate[] {
														new PropertyTemplate<BoundFunctionCallExpression>(
															"Condition",
															new ObjectTemplate<BoundFunctionCallExpression>(
																new MemberTemplate[] {
																	new PropertyTemplate<BoundMethodCallFunction>(
																		"Function",
																		new ObjectTemplate<BoundMethodCallFunction>(
																			new MemberTemplate[] {
																				new PropertyTemplate<MethodInfo>(
																					"Method",
																					new ObjectTemplate<MethodInfo>(
																						new MemberTemplate[] {
																							new PropertyTemplate<string>(
																								"Name",
																								new NodeEqualityTemplate<string>("MoveNext")
																								)
																						})
																					)
																			})
																		),
																	new PropertyTemplate<IEnumerable<Expression>>(
																		"Arguments",
																		new EnumerableTemplate<Expression>(
																			new ElementTemplate[] {
																				new ElementTemplate<BoundVariableReferenceExpression>(
																					0,
																					new ObjectTemplate<BoundVariableReferenceExpression>(
																						new MemberTemplate[] {
																							new PropertyTemplate<LocalVariable>(
																								"Variable",
																								new NodeEqualityTemplate<LocalVariable>(enumeratorVariableTemplate.CapturedValue)
																								)
																						})
																					)
																			})
																		)
																})
															),
														new PropertyTemplate<IEnumerable<Statement>>("Statements", loopStatementsTemplate)
													})
												)
										})
									)
							});

						isForEachLoop = tryBlockTemplate.Inspect(exceptionFlow.TryBlock);

						if (isForEachLoop)
						{
							context.Statements.Remove(previousStatement);

							IteratorLoop loop = new IteratorLoop();
							loop.Source = enumerableExpressionTemplate.CapturedValue;
							loop.ElementVariable = itemVariableTemplate.CapturedValue;
							loop.Statements.AddRange(loopStatementsTemplate.CapturedValue);
							loop.Statements.Remove(itemVariableDeclarationTemplate.CapturedValue);
							this.Context.RecordStatement(loop);
						}
					}

					if (!isForEachLoop)
					{
						this.ActiveStatementBlock.Add(exceptionFlow.TryBlock);
					}
				}

				AnalyzeInstruction();

				if (inBody
					&& (
						(context.CurrentInstruction == null)
						|| (!this.BodyContains(context.CurrentOffset))
					)
					)
				{
					inBody = false;
					this.OnExitBody();
				}
			}

			//{
			//    ExceptionHandlingClause tryBlock = this.GetLongestTryBlock(block.StartOffset);

			//    if (doLoopAnalyzer != null)
			//    {
			//        this.AddStatement(doLoopAnalyzer.LoopStatement);
			//        return doLoopAnalyzer.AnalyzeBlock(block);
			//    }
			//}

			//// Add statements
			//this.AnalyzeStatements(block);

			//return AnalyzeExit(block);

			this.OnExitBlock();
		}

		protected virtual void AnalyzeInstruction()
		{
			MsilDecompilationContext context = this.Context;
			Instruction instruction = context.CurrentInstruction;

			context.OpCodeHandler(context);
			bool isBranch = (instruction.OpCode.FlowControl == FlowControl.Branch);
			bool isConditionalBranch = (instruction.OpCode.FlowControl == FlowControl.Cond_Branch);
			if (isBranch || isConditionalBranch)
			{
				MsilSwitch @switch = context.GetTransitionAt(instruction.Offset) as MsilSwitch;

				if (@switch != null)
				{
					this.AnalyzeSwitch(@switch);
				}
				else
				{
					MsilBranch branch = (MsilBranch)context.GetTransitionAt(instruction.Offset);
					if (isConditionalBranch)
					{
						this.AnalyzeLocalConditionalBranch(branch);
					}
					else
					{
						this.AnalyzeLocalBranch(branch);
					}
				}
			}
			else
			{
				context.Advance();
			}
		}

		protected bool IsBranchToSubsequentBlock(MsilBranch branch)
		{
			if (branch == null) throw new ArgumentNullException("branch");

			if (branch.TargetOffset == this.BlockEndOffset) return true;

			// Check for an unconditional branch immediately following this block
			MsilBranch branchAfterBlock = this.Context.GetTransitionAt(this.BlockEndOffset) as MsilBranch;
			Instruction instruction =
				(this.BlockEndOffset < this.Context.MethodLength)
				? this.Context.GetInstructionAt(this.BlockEndOffset)
				: null
				;

			bool isToNextBlock =
				(branchAfterBlock != null)
				&& (instruction != null)
				&& (branchAfterBlock.TargetOffset == branch.TargetOffset)
				&& (instruction.OpCode.FlowControl == FlowControl.Branch)
				;

			return isToNextBlock;
		}

		//protected virtual MsilFlowAnalyzer AnalyzeExit(MsilBlock block)
		//{
		//    // Determine ending
		//    if (
		//        (block.ExitTransitions.Count == 1)
		//        )
		//    {
		//        MsilTransition exitTransition = block.ExitTransitions[0];
		//        if (exitTransition.Type == MsilTransitionType.Branch)
		//        {
		//            return this.AnalyzeExitBranch(exitTransition);
		//        }
		//        else
		//        {
		//            return this;
		//        }
		//    }
		//    else if (block.ExitTransitions.Count > 1)
		//    {
		//        // Check for IF block
		//        MsilTransition fallthroughTransition = null;
		//        MsilTransition branchTransition = null;
		//        foreach (var transition in block.ExitTransitions)
		//        {
		//            if (transition.Type == MsilTransitionType.Fallthrough) fallthroughTransition = transition;
		//            else if (transition.Type == MsilTransitionType.Branch) branchTransition = transition;
		//        }

		//        Debug.Assert(fallthroughTransition != null);
		//        Debug.Assert(branchTransition != null);

		//        MsilConditionFlowAnalyzer conditionFlow = new MsilConditionFlowAnalyzer(this, branchTransition.Condition, block.StartOffset, fallthroughTransition.TargetOffset, branchTransition.TargetOffset);

		//        return conditionFlow;
		//        //MsilBlock fallthroughBlock = this.Context.GetBlockAtOffset(fallthroughTransition.TargetOffset);
		//        //MsilBlock branchBlock = this.Context.GetBlockAtOffset(branchTransition.TargetOffset);

		//        //Debug.Assert(fallthroughBlock != null);
		//        //Debug.Assert(branchBlock != null);

		//        //if (branch
		//    }

		//    return this;
		//}

		//protected void AnalyzeStatements(MsilBlock block)
		//{
		//    foreach (var statementFrame in block.StatementFrames)
		//    {
		//        Statement statement = statementFrame.Statement;

		//        Variable assignedVariable = null;
		//        AssignmentStatement assignment = statement as AssignmentStatement;
		//        if (assignment != null)
		//        {
		//            BoundVariableReferenceExpression variableRef = assignment.Variable as BoundVariableReferenceExpression;
		//            if (variableRef != null)
		//            {
		//                assignedVariable = variableRef.Variable;
		//            }
		//        }

		//        // Check variable references
		//        foreach (var variableReference in statementFrame.VariableReferences)
		//        {
		//            LocalVariable variable = variableReference.Variable as LocalVariable;
		//            if (!this.Context.DeclaredVariables.Contains(variable))
		//            {
		//                VariableDeclarationStatement declaration = new VariableDeclarationStatement(new BoundTypeReference(variable.LocalInfo.LocalType), variable.Name);
		//                if (variable == assignedVariable)
		//                {
		//                    declaration.Initializer = assignment.Initializer;
		//                    statement = declaration;
		//                }
		//                else
		//                {
		//                    this.AddStatement(declaration);
		//                }
		//                this.Context.DeclaredVariables.Add(variable);
		//            }
		//        }

		//        this.AddStatement(statement);
		//    }
		//}
	}
}
